This document discusses a few of the design issues and especially shortcomings in the implementation of linking in the DrawEditor sample. DrawEditor, as all sample code, should be viewed at this time as a work in progress. Completeness, and elegance of implementation may have been sacrificed in order provide useful working examples of how to implement some OpenDoc linking features in the same time frame as the OpenDoc 1.0 release.
What Linking features DrawEditor does and does not support
DrawEditor supports creation, and persistent storage of links consisting of intrinsic, mixed, or embedded content.. It supports multiple, overlapping link source content. Links can initially be created using ‘Copy’ and ‘Paste As’, or command-drag. (One can create a cross-document link using command-drag, but the destination document must be activated first in order for the paste-as dialog to be displayed.) Existing links can be propagated by Cut or Copy and Paste, or by Drag and Drop, according to the rules for link propagation spelled out in the OpenDoc Human Interface Specification for the Macintosh Implementation.
It also supports ‘Undo’ and ‘Redo’ of link creation. It prevents modification of link destination content, and prevents moving or removing of partial destinations by the user, while allowing fully selected link destination’s to be moved and removed.
The most conspicuously absent of the linking U.I. features is link borders. There just wasn’t time to implement that for this version.
Another shortcut that was taken was to not implement the two special dialogs which are specified for use when the user attempts to modify link destination content. Instead, when the editing is being prevented by exactly one link destination, the Destination Link Info dialog for that destination is displayed. This provides a super set of the required capabilities (break link, find source), but not the special text associated with attempting to edit destination content. In the case that more than one destination is involved in the edit attempt, DrawEditor simply beeps, rather than posting the specified alert.
The Link Info menu item is enabled when the selection contains content from exactly one link destination, or when it completely contains the content of exactly one link source. The result of this is that it is possible to create configurations of source and destination content in which the Link Info for one or more link sources is inaccessible.
A somewhat more useful algorithm would be to enable Link Info for whichever Link Source or Link Destination had the largest number of shapes selected. However, there would still be scenarios in which one link would be masked by another. In addition, a link consisting of a single embedded frame, causes the ‘Part Info’ dialog for that frame to be inaccessible. This last could be solved if we showed link borders, and distinguished between clicking in a link border and clicking in the embedded frame border.
Another H.I. Spec concept which is not implemented, is that of selecting a link source itself as opposed to its content. For example, this is would allow the entire content to be removed from and then replaced in an existing link source without breaking the link. I t also could allow content to drawn, pasted or dropped ‘into a link source’. In DrawEditor, when the entire content of either a link source or link destination is selected, then the link is considered to be selected. If removed, then the link is removed with it. Undo then restores both the content and any associated links.
The use of the kODPropCloneKindUsed property has only the minimal implementation. The property is written as required prior to cloning a single embedded frame, in order to support the use of that property by the embedded frame in the event that it chooses to promise its content. The rest of this recipe, which requires using this property when fulfilling a promise was not necessary, becauseDrawEditor does not write promises in its CloneInto method.
As with all data transfer operations in DrawEditor, link updating does not always preserve the ordering of shapes that determines how they are drawn when they overlap.
Some Implementation Notes
One striking feature of the implementation, is that the two classes which implement the internalized representation and behavior of links, CPublishLink and CSubscribeLink are not called CLinkSource and CLinkDestination. The present terminology clearly has roots in Edition Manager concepts. Most recently, it was inherited from early versions of the OpenDoc Framework (ODF) from which some of the architecture for supporting linking was derived.
A second notable feature is the complexity involved in maintaining content lists for CPublishLink objects, and for command objects responsible for undoing and redoing, additions, and removal of content. When possible, the best way to avoid some of this complexity is to design linking into your content model from the beginning, and not just add it in to an existing non-linking content model. In the case of DrawEditor, a hierarchical model in which a CSubscribeLink was actually a compound ‘shape’ would have greatly simplified the implementation. Hopefully, the following somewhat detailed discussion of this implementation will help you to anticipate some key issues early in your design process.
In OpenDoc, undoing a link source content change which precipitated a link update does not cause the update to be undone, thus restoring the destination to precisely its original state. Instead it simply causes another update to occur. The resultant content objects will have the same data as the originals, but will not be the same objects.
For this reason it is essential that any command data which will be used in subsequent undo or redo actions contain no direct references to shapes contained in link destinations. Instead, such commands capture direct references only to shapes which are not involved in any link destination, and capture a separate list of CSubscribeLink references. The CSubscribeLink class provides an interface to perform the required actions on the contained shapes for undoing, redoing and committing of these commands. This approach works, because none of the undoable actions can ever involve partial link destination content.
The other objects which are affected by object replacement during updates are CPublishLinks, which can contain one or more complete link destinations. Since a CPublishLink has to externalize its content, and since the methods for externalizing content, which are common to all data transfer operations, are based upon a simple list of shapes it maintains a list that includes both ‘Subscribed’ and ‘Unsubscribed’ shapes.
To avoid dangling references following an update, as shapes are removed from and added to the part’s content model, they are dynamically replaced in any containing link sources. This mechanism depends upon the lists of containing link sources that are maintained by each shape and each CSubscribeLink.
However, a CPublishLink has an ‘unpublished’ state after its creation when a link Spec is written to a data transfer object, but before CreateLink is called, and also after the user chooses to break the link. In this state, it needs to maintain the data necessary to publish or republish itself, but the references to the CPublishLink in contained shapes and link destinations which support dynamic replacement of link source content don’t exist.
Instead, the CPublishLink, when created in conjunction with writing an ODLinkSpec, begins with a list of unsubscribed shapes and a list of contained subscribe links, derived directly from those captured by the command object that created it. Its ‘Publish’ and ‘Unpublish’ methods are mutually inverse operations that convert its content between this unpublished format, and the flat list of shapes which is easily externalized, as well as adding and removing references to itself to or from the contained shapes and CSubscribeLink objects.
Known Problem
• DrawEditor caches the value returned by ODFrame::GetLinkStatus in its implementation of ODPart::LinkStatusChanged, and uses the cached value in determining whether to allow editing, and whether to call ODFrame::ContentUpdated. However, it does not initialize the cached value by calling ODFrame::GetLinkStatus when its display frame is first connected.
When first internalized, an embedded frame is likely to already have the link status that its containing part is going to initialize it to, in which case, no LinkStatusChanged notification will be received, and the cached value will be out of synch.
The resultant behavior applies to DrawEditor frames embedded in link sources and destinations after they have been internalized as a result of opening a saved document or dropping or pasting content. In this situation, modifications to content in the part embedded in the link source will not trigger a link update, and attempts to modify content in the part embedded in a link destination will not be prevented. Behavior when editing within the containing part(s) that maintain the links is correct.
The behavior in link destinations is corrected with the first update. Both source and destination behavior can be corrected by selecting the linked content in the part that owns the link, and using ‘Link Info’ to break the link, then choosing ‘Undo Break Link’. (But see above for limitations on the accesibility of Link Info in Draw Edtitor).
• Documents which DrawEditor link source when opened will produce a ref-count warrning when closed. (OpenDoc Debug build only, otherwise it’s silent)